/*
 * Copyright 2002-2005 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lib.jsonrpc;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.Enhancer;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;


// TODO: Auto-generated Javadoc
/**
 * The Class ServiceProxy is used by client side to create proxy object by some
 * interface. client sides use this object invoke remote service methods.
 * 
 * 
 * @author jay liu
 * @since 02.10.2010
 * 
 */
public class ServiceProxy implements MethodInterceptor {
  /**
   * RFC 4627 say: "The MIME media type for JSON text is application/json".
   * See link https://www.ietf.org/rfc/rfc4627.txt
   */
  public static final String JSON_CONTENT_TYPE = "application/json; charset=utf-8";

	/** The Constant log. */
	private static final Logger log = Logger.getLogger(ServiceProxy.class
			.getName());

	
	/** The next id. */
	int nextId = 0;
	
	/** The url. */
	String url = null;
	
	
	
	/** The e. */
	Enhancer e = null;
	
	/** The factory. */
	ObjectFactory factory;

	/**
	 * Instantiates a new service proxy.
	 *
	 * @param serviceInterface the service interface
	 * @param url the url
	 */
	public ServiceProxy(Class<?> serviceInterface, String url) {
		this(serviceInterface, new BasicObjectFactory(), url);
	}
			
	/**
	 * Instantiates a new service proxy.
	 *
	 * @param serviceInterface the service interface
	 * @param factory the factory
	 * @param url the url
	 */
	public ServiceProxy(Class<?> serviceInterface, ObjectFactory factory, String url) {
		this.factory = factory;
		this.url = url;
		e = new Enhancer();
		e.setSuperclass(serviceInterface);
		e.setCallback(this);
	}

	/**
	 * Creates the.
	 *
	 * @return the object
	 */
	public Object create() {
		return e.create();
	}

	/* (non-Javadoc)
	 * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], net.sf.cglib.proxy.MethodProxy)
	 */
	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws RPCException {

		try {
			JSONArray jargs = Utils.encodeArguments(method, args);
			return post(method, jargs);
		} catch (JSONException e) {
			log.log(Level.WARNING, e.getMessage(), e);
			throw new RPCException(RPCException.PARSE_ERROR, e.getMessage(), e);
		} catch (IllegalAccessException e) {
			log.log(Level.WARNING, e.getMessage(), e);
			throw new RPCException(RPCException.METHOD_NOT_FOUND, e.getMessage(), e);
		} catch (InvocationTargetException e) {
			log.log(Level.WARNING, e.getMessage(), e);
			throw new RPCException(RPCException.INTERNAL_ERROR, e.getMessage(), e);
		} catch (IOException e) {
			log.log(Level.WARNING, e.getMessage(), e);
			throw new RPCException(RPCException.INTERNAL_ERROR, e.getMessage(), e);
		} catch (InstantiationException e) {
			log.log(Level.WARNING, e.getMessage(), e);
			throw new RPCException(RPCException.INTERNAL_ERROR, e.getMessage(), e);
		}

	}
	
	/**
	 * Next id.
	 *
	 * @return the int
	 */
	private synchronized int nextId(){
		nextId += 1;
		return nextId;
	}
	

	/**
	 * Post.
	 *
	 * @param method the method
	 * @param args the args
	 * @return the object
	 * @throws JSONException the jSON exception
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws InstantiationException the instantiation exception
	 * @throws IllegalAccessException the illegal access exception
	 * @throws InvocationTargetException the invocation target exception
	 */
	private Object post(Method method, JSONArray args) throws JSONException,
			IOException, InstantiationException, IllegalAccessException,
			InvocationTargetException {

		StringBuffer sb = new StringBuffer();
		HttpURLConnection connection = null;
		OutputStreamWriter writer = null;
		InputStreamReader reader = null;

		int responseCode = 200;

		try {
			JSONObject packet = new JSONObject();
			packet.put("jsonrpc", "2.0");
			packet.put("method", method.getName());
			packet.put("params", args);
			packet.put("id", nextId());
			
			log.info("-------invoke:" + packet.toString());

			sb = new StringBuffer();
			char[] buff = new char[1024];

			URL u = new URL(url);
			connection = (HttpURLConnection) u.openConnection();

			connection.setRequestMethod("POST");
			//connection.setRequestProperty("Content-Type",
			//		"application/x-www-form-urlencoded");
			connection.setRequestProperty("Content-Type", JSON_CONTENT_TYPE);
			connection.setDoOutput(true);

			writer = new OutputStreamWriter(connection.getOutputStream(),
					"UTF-8");
			writer.write(packet.toString());
			writer.flush();

			responseCode = connection.getResponseCode();

			if (responseCode == HttpURLConnection.HTTP_OK) {
				reader = new InputStreamReader(connection.getInputStream(),
						"UTF-8");
				int i = 0;
				while ((i = reader.read(buff, 0, 1024)) != -1) {
					sb.append(buff, 0, i);
				}
				log.info(sb.toString());
				String val = sb.toString();
				JSONObject jobj = new JSONObject(val);
				return Utils.parseReturnValue(method, jobj, factory);
			} else
				throw new RPCException(RPCException.INTERNAL_ERROR, "response code is:" + responseCode);
		} finally {
			if (writer != null)
				try {
					writer.close();
				} catch (Exception e) {
				}
			if (reader != null)
				try {
					reader.close();
				} catch (Exception e) {
				}
			if (connection != null) {
				try {
					connection.disconnect();
				} catch (Exception e) {
				}
			}
		}

	}

}
